﻿-- local showDialog = require"far2.tableview"
local F = far.Flags

-- Регулярка, позволяющая получить в 5 подряд идущих переменных время из SRT-формата
local srt_time_regex = "(-)?\\s*(?:(?:(?:(\\d+)\\s*:\\s*)?(\\d+)\\s*:\\s*)?(\\d+))?(?:\\s*[,.]\\s*(\\d{1,3}))?";
local srt_time_string_regex = "^("..srt_time_regex.."\\s+-->\\s+"..srt_time_regex..")"


local T = {
    stretch = "stretch",
    shift = "shift",
}

-- Преобразовывает время (со знаком) в миллисекунды
local function Ms(sign,hh,mm,ss,ms)
    local res
    if not ms or ms:len() < 1 then
    	ms = 0
    elseif ms:len() == 1 then
    	ms = ms * 100
    elseif ms:len() == 2 then
    	ms = ms * 10
    else
    	ms = 1 * ms:sub(0,3)
    end
    res = 1000 * (3600*(hh or 0) + 60*(mm or 0) + (ss or 0)) + ms
    return sign == "-" and -res or res
end

-- Печатает время в SRT-формате
local function TimeToSrt(time)
    local sign = time > 0 and "" or "-"
    time = math.abs(time)
    local ms = time % 1000
    time = math.floor(time/1000);
    local ss = time % 60
    time = math.floor(time/60)
    local mm = time % 60
    local hh = math.floor(time/60)
    return ("%s%02d:%02d:%02d,%03d"):format(sign,hh,mm,ss,ms)
end

local function ParseLine(ln)
    ln = ln or Editor.CurLine
    local line = editor.GetString(-1,ln).StringText
    local match, ssig,sh,sm,ss,sms, esig,eh,em,es,ems = regex.match(line, srt_time_string_regex)
    return match and {
        ln = ln,
        line = line,
        match = match,

        ssig = ssig,
        sh = sh,
        sm = sm,
        ss = ss,
        sms = sms,
        
        esig = esig,
        eh = eh,
        em = em,
        es = es,
        ems = ems,

        stime = Ms(ssig,sh,sm,ss,sms),
        etime = Ms(esig,eh,em,es,ems),
    } or nil
end

-- Ищет ближайшую строку со временем к указанной строке (или к текущей, если не строка не указана)
local function Closest(start, time)
    start = start or Editor.CurLine
    local parsed
    local finish = Editor.Lines
    for ln = start, finish, 1 do
        parsed = ParseLine(ln)
        if parsed and (not time or time <= parsed.stime) then
            return parsed
        end
    end
end


-- Производит изменене субтитров согласно последовательности команд
local function Transform(starttime, transform_sequence)
    local ti, tv
    local stime, etime, newtime
    local finish = Editor.Lines

	editor.UndoRedo(-1, "EUR_BEGIN")
    for ln = 1, finish, 1 do
        parsed = ParseLine(ln)
        if parsed and parsed.stime >= starttime then
            for ti, tv in ipairs(transform_sequence) do
                if tv[1] == T.shift then
                    stime = parsed.stime + tv[2]
                    etime = parsed.etime + tv[2]
                elseif tv[1] == T.stretch then
                    stime = parsed.stime * tv[2]
                    etime = parsed.etime * tv[2]
                end
            end

            local newtime = TimeToSrt(stime).." --> "..TimeToSrt(etime)
            -- Производит поиск и замену, так как в строке могут быть другие, важные символы (пример - ass/ssa сабы)
            editor.SetString(-1, ln, mf.replace(parsed.line, parsed.match, newtime, 1))
        end
    end
	editor.UndoRedo(-1, "EUR_END")
end

-- Читает время в SRT-формате и возвращает миллисекунды
local function SrtToTime(srttime)
	if not srttime then
		return nil
	else
	    return Ms(regex.match(srttime, "^"..srt_time_regex))
	end
end

local function EnterTime(title)
    return SrtToTime(far.InputBox(nil, title or "Введіть час", "Введіть час в форматі SRT (hh:mm:ss,xxx):", nil, ""))
end

local function EnterNumber(title)
    local res = far.InputBox(nil, title or "Введіть число", "Введіть число у вигляді арифметичного выразу:", nil, "")
	if res then
	    return loadstring("return " .. res )()
	end
    return nil
end

local function Shift(shift)
    shift = shift or EnterTime("Зсуваємо від курсору...")
    if not shift then return end

    local starttime = Closest()
    if not starttime then return end


    Transform(starttime.stime, {
        {T.shift, shift},
    })
end

local function Stretch(stretch)
    stretch = stretch or EnterNumber("Розтягнути від курсору...")
    if not stretch then return end

    local starttime = Closest()
    if not starttime then return end

    Transform(starttime.stime, {
        {T.stretch, stretch},
    })
end

local function GoToTime(time)
    time = time or EnterTime("Перейти до часу...")
    if time == nil then return end

    found = Closest(1, time)
    if found then
        local info = editor.GetInfo(-1)

        local posdata = {
            CurLine = found.ln,
            CurPos = info.CurPos,
            CurTabPos = info.CurTabPos,
            TopScreenLine = (info.CurLine - info.TopScreenLine > found.ln) and 1 or (found.ln - info.CurLine + info.TopScreenLine),
            LeftPos = info.LeftPos,
            Overtype = info.Overtype,
        }
        editor.SetPosition(-1, posdata)
    end
end

local function Subtitles()
    local item, pos = far.Menu({Title="Робота з субтитрами",Flags={}}, {
--[[1]] {text="&m. Зсунути від курсору..."},
--[[2]] {text="&s. Розтягнути від курсору..."},
--[[3]] {text="&g. Перейти до часу..."},
    })
    if pos == 1 then
        Shift()
    elseif pos == 2 then
        Stretch()
    elseif pos == 3 then
        GoToTime()
    end
end

Macro {
    area="Editor";
    key="CtrlAltS";
    flags="DisableOutput";
    description="Subtitle editor";
    action=Subtitles;
}
